package com.kinyx.framework.reflect;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.Note;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.bean.DKHashMap;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.utils.KArrayUtils;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
 * 字段工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KFieldUtils {

	/** 缓存列表 */
	static final Map<Class<?>, List<Field>> CACHE_LIST = new HashMap<>();
	/** 缓存 */
	static final DKHashMap<Class<?>, String, Object> CACHE = new DKHashMap<>();

	/**
	 * 获取字段列表
	 * @param clazz 类型
	 * @param distinct 子类与父类有定义相同字段时，true: 去重; false: 不去重;
	 * @param staticType true: 包含static修饰的字段; false: 不包含static修饰的字段;
	 * @param finalType true: 包含final修饰的字段; false: 不包含final修饰的字段;
	 * @param excludeFieldNames 排除字段名
	 * @return 字段列表
	 */
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_getFieldList_0_getField")
	public static List<Field> getFieldList(@CanNotNull final Class<?> clazz, @CanNotNull final boolean distinct, @CanNotNull final boolean staticType, @CanNotNull final boolean finalType, @CanNull @Note("取全字段时忽略该参数") final String... excludeFieldNames) {
		final boolean standard = distinct && !staticType && !finalType && KArrayUtils.isEmpty(excludeFieldNames);
		if (standard) {// 缓存过则直接返回
			final List<Field> cached = CACHE_LIST.get(clazz);
			if (cached != null) { return cached; }
		}
		final List<Field> listFields = new ArrayList<>();
		final List<Class<?>> classes = KClassUtils.getExtendsClasses(clazz);// 获取继承类
		classes.add(0, clazz);// 加入当前类
		// 遍历
		classes.forEach(currentClass -> {
			final Field[] currentFields = currentClass.getDeclaredFields();
			// 遍历当前类所有字段
			loop: for (final Field currentField : currentFields) {
				final String currentFieldName = currentField.getName();
				// 遍历所有排除字段
				if (excludeFieldNames != null) {
					for (final String excludeFieldName : excludeFieldNames) {
						if (currentFieldName.equals(excludeFieldName)) { continue loop; } // 需要排除的字段，则忽略。
					}
				}
				// 遍历所有已存字段
				if (distinct) {
					for (final Field field : listFields) {
						if (currentFieldName.equals(field.getName()) && field.getType().equals(currentField.getType())) {
							continue loop;// 去重
						}
					}
				}
				// 验证修饰符类型
				final int modifiers = currentField.getModifiers();
				if (!staticType && Modifier.isStatic(modifiers)) { continue loop; } // static
				if (!finalType && Modifier.isFinal(modifiers)) { continue loop; } // final
				// 添加字段
				listFields.add(currentField);
				if (standard) {// 标准取值情况下，缓存该字段。
					CACHE.put(clazz, currentField.getName(), currentField);// 缓存
				}
			}
		});
		if (standard) {// 标准取值情况下，缓存该字段列表。
			CACHE_LIST.put(clazz, listFields);// 缓存
		}
		return listFields;
	}

	/**
	 * 取字段
	 * @param clazz 类型
	 * @param fieldName 字段名
	 * @param staticType true: 包含static修饰的字段; false: 不包含static修饰的字段;
	 * @param finalType true: 包含final修饰的字段; false: 不包含final修饰的字段;
	 * @return 字段
	 */
	@ReturnNull("未取得字段")
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_cache")
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_getFieldList_0_getField")
	public static Field getField(@CanNotNull final Class<?> clazz, @CanNotNull final String fieldName, @CanNotNull final boolean staticType, @CanNotNull final boolean finalType) {
		if (!staticType && !finalType) {// 标准取值情况下
			// 尝试从缓存中取
			final Object cached = CACHE.get(clazz, fieldName);
			if (cached == null) {
				// 没有缓存值
				if (CACHE_LIST.containsKey(clazz)) {// 缓存过列表，说明该类中没有该字段。
					CACHE.put(clazz, fieldName, Boolean.FALSE);// 则先缓存单个值为false
					return getField(clazz, fieldName, staticType, finalType);
				}
				// 先缓存列表
				getFieldList(clazz, true, staticType, finalType);
				// 再次取值
				return getField(clazz, fieldName, staticType, finalType);
			} else {
				// 有缓存值，则直接返回。
				return Boolean.FALSE.equals(cached) ? null : (Field) cached;
			}
		} else {// 非标准取值情况下，直接反射取值。
			final List<Field> list = getFieldList(clazz, true, staticType, finalType);
			for (final Field field : list) {
				if (field.getName().equals(fieldName)) {
					return field;
				}
			}
			// 字段列表中未匹配到该字段
			return null;
		}
	}

	/**
	 * 取字段
	 * @param clazz 类型
	 * @param fieldNameObj 字段名定义对象，使用toString()确定字段名。
	 * @param staticType true: 包含static修饰的字段; false: 不包含static修饰的字段;
	 * @param finalType true: 包含final修饰的字段; false: 不包含final修饰的字段;
	 * @return 字段
	 */
	@ReturnNull("未取得字段")
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_getFieldList_0_getField")
	public static Field getField(@CanNotNull final Class<?> clazz, @CanNotNull final Object fieldNameObj, @CanNotNull final boolean staticType, @CanNotNull final boolean finalType) {
		return getField(clazz, fieldNameObj.toString(), staticType, finalType);
	}

	/**
	 * 获取实例对象的字段值
	 * @param obj 实例对象
	 * @param field 字段
	 * @return 值
	 */
	@ReturnNull("取得的值为null值")
	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalAccessException", when = "Field.get(Object)")
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_get_set")
	public static Object get(@CanNotNull final Object obj, @CanNotNull final Field field) {
		try {
			// 尝试通过Getter获取值
			return new PropertyDescriptor(field.getName(), field.getDeclaringClass()).getReadMethod().invoke(obj);
		} catch (final Exception e) {
			// 尝试通过字段直接获取值
			if (!field.isAccessible()) { field.setAccessible(true); } // 强制开启访问权限
			try {
				return field.get(obj);// 取值
			} catch (final IllegalArgumentException ex) {
				throw new KRuntimeException(ex);
			} catch (final IllegalAccessException ex) {// never happens
				throw new KRuntimeException(ex, ErrorCodeX.NeverHappens);
			}
		}
	}

	/**
	 * 获取实例对象的字段值
	 * @param obj 实例对象
	 * @param fieldName 字段名
	 * @return 值
	 */
	@ReturnNull("取得的值为null值")
	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalAccessException", when = "Field.get(Object)")
	public static Object get(@CanNotNull final Object obj, @CanNotNull final String fieldName) {
		return get(obj, getField(obj.getClass(), fieldName, false, false));
	}

	/**
	 * 获取实例对象的字段值
	 * @param obj 实例对象
	 * @param fieldNameObj 字段名定义对象，使用toString()确定字段名。
	 * @return 值
	 */
	@ReturnNull("取得的值为null值")
	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalAccessException", when = "Field.get(Object)")
	public static Object get(@CanNotNull final Object obj, @CanNotNull final Object fieldNameObj) {
		return get(obj, getField(obj.getClass(), fieldNameObj, false, false));
	}

	/**
	 * 设置值
	 * @param obj 实例对象
	 * @param field 字段
	 * @param value 值
	 */
	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	private static void _set(@CanNotNull final Object obj, @CanNotNull final Field field, @CanNotNull final Object value) {
		try {
			// 尝试通过Setter设置值
			new PropertyDescriptor(field.getName(), obj.getClass()).getWriteMethod().invoke(obj, value);
		} catch (final Exception e) {
			// 尝试通过字段直接设置值
			if (!field.isAccessible()) { field.setAccessible(true); } // 强制开启访问权限
			try {
				field.set(obj, value);// 设置值
			} catch (final IllegalArgumentException ex) {
				throw new KRuntimeException(ex);
			} catch (final IllegalAccessException ex) {// never happens
				throw new KRuntimeException(ex, ErrorCodeX.NeverHappens);
			}
		}
	}

	/**
	 * 设置值
	 * @param obj 实例对象
	 * @param field 字段
	 * @param value 值
	 */
	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	@Tester("com.kinyx.framework.reflect.KFieldUtils_Tester_get_set")
	public static void set(@CanNotNull final Object obj, @CanNotNull final Field field, @CanNotNull final Object value) {
		if (value == null) { _set(obj, field, value); return; } // null值直接设置
		final Class<?> fieldType = field.getType();
		final Class<? extends Object> valueType = value.getClass();
		if (fieldType.equals(valueType)) {// 同类型直接设置
			_set(obj, field, value);
		} else {//  不同类型进行转换后设置
			_set(obj, field, KTypeConverter.convert(value, fieldType));
		}
	}

	/**
	 * 设置值
	 * @param obj 实例对象
	 * @param fieldName 字段名
	 * @param value 值
	 */
	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	public static void set(@CanNotNull final Object obj, @CanNotNull final String fieldName, @CanNotNull final Object value) {
		set(obj, getField(obj.getClass(), fieldName, false, false), value);
	}

	/**
	 * 设置值
	 * @param obj 实例对象
	 * @param fieldNameObj 字段名定义对象，使用toString()确定字段名。
	 * @param value 值
	 */
	@Note("使用fieldName.toString()确定fieldName")
	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	public static void set(@CanNotNull final Object obj, @CanNotNull final Object fieldNameObj, @CanNotNull final Object value) {
		set(obj, getField(obj.getClass(), fieldNameObj, false, false), value);
	}

}
